import fs from "fs";
import axios from "axios";
import xlsx from "node-xlsx";
import express from "express";
import { PrismaClient, Prisma, user as User, pay_info as PayInfo } from "@prisma/client";
import { CreateId, Check, Result, Time, WeChat, redis } from "@/utils";
import { payStatusConversion } from "@/config/constant";
import { EnumPayInfo } from "@/Enum";

const prisma = new PrismaClient();
const router = express.Router();

/** 创建支付信息 */
router.post("/create", async (req, res) => {
   const { client_id, fee, level, school, category, major, batch, school_year, merchant, name }: PayInfo.Create = req.body;
   if (!Check.Required([client_id, fee, level, school, category, major, batch, school_year, merchant, name])) {
      return res.send(Result.Error("参数错误"));
   }

   const userId = req.userId;

   const client = await prisma.client.findUnique({ where: { id: client_id } });

   if (!client) return res.send(Result.Error("当前用户不存在"));
   if (!client.phone) return res.send(Result.Error("当前用户不存在"));

   const pay_id = await CreateId.One("pay_info");

   await prisma.pay_info.create({
      data: {
         pay_id,
         merchant,
         client_id,
         fee,
         name,
         level,
         school,
         category,
         major,
         batch,
         school_year,
         phone: client.phone,
         created_by: userId,
         created_time: Time.Now(),
         status: "OPEN"
      }
   });

   res.send(Result.OK());
});

/** 取消订单 */
router.post("/delete", async (req, res) => {
   const { pay_id }: PayInfo.Delete = req.body;
   if (!Check.Required([pay_id])) return res.send(Result.Error("参数错误"));

   const { userId } = req;

   await prisma.pay_info.update({
      where: { pay_id },
      data: { status: "CLOSE", updated_by: userId, updated_time: Time.Now() }
   });

   res.send(Result.OK());
});

/** 修改订单 */
router.post("/update", async (req, res) => {
   const { pay_id, fee, level, school, category, major, batch, school_year, name, merchant }: PayInfo.Update = req.body;
   if (!Check.Required([pay_id, fee, level, school, category, major, batch, school_year, name, merchant])) {
      return res.send(Result.Error("参数错误"));
   }

   const { userId } = req;
   const oldValue = await prisma.pay_info.findUnique({ where: { pay_id } });
   if (!oldValue) return res.send(Result.Error("当前条目不存在"));

   await prisma.pay_info.update({
      where: { pay_id: pay_id },
      data: {
         fee: fee || oldValue.fee,
         name: name || oldValue.name,
         merchant: merchant || oldValue.merchant,
         level: level || oldValue.level,
         school: school || oldValue.school,
         category: category || oldValue.category,
         major: major || oldValue.major,
         batch: batch || oldValue.batch,
         school_year: school_year || oldValue.school_year,
         updated_by: userId,
         updated_time: Time.Now()
      }
   });

   res.send(Result.OK());
});

/** 查询支付信息列表 */
router.get("/search", async (req, res) => {
   const { form, laypage }: PaginationSearch<PayInfo.Search> = req.query as any;

   const where: Prisma.pay_infoWhereInput = {
      AND: [
         !!form.name ? { phone: form.name } : {},
         !!form.level ? { level: form.level } : {},
         !!form.school ? { school: form.school } : {},
         !!form.category ? { category: form.category } : {},
         !!form.major ? { major: form.major } : {},
         !!form.status ? { status: form.status } : {},
         !!form.created_by ? { created_by: form.created_by } : {},
         !!form.deadline
            ? { AND: [{ deadline: { gte: new Date(form.deadline[0]) } }, { deadline: { lte: new Date(form.deadline[1]) } }] }
            : {},
         !!form.created_time
            ? {
                 AND: [
                    { created_time: { gte: new Date(form.created_time[0]) } },
                    { created_time: { lte: new Date(form.created_time[1]) } }
                 ]
              }
            : {},
         !!form.prepay_create_time
            ? {
                 AND: [
                    { prepay_create_time: { gte: new Date(form.prepay_create_time[0]) } },
                    { prepay_create_time: { lte: new Date(form.prepay_create_time[1]) } }
                 ]
              }
            : {}
      ]
   };

   const total = await prisma.pay_info.count({ where });
   const payInfo = await prisma.pay_info.findMany({
      where,
      skip: (laypage.pageNum - 1) * laypage.pageSize,
      take: laypage.pageSize * 1,
      orderBy: {
         created_time: "desc"
      }
   });

   // 获取所有用户列表，并将数组转为 map
   const userList = await prisma.user.findMany();
   const userMap = new Map<string, User>();
   userList.forEach(item => userMap.set(item.id, item));

   const list = payInfo.map(item => {
      const created_by = userMap.get(item.created_by);

      return {
         ...item,
         deadline: Time.Format(item.deadline),
         created_time: Time.Format(item.created_time),
         prepay_create_time: Time.Format(item.prepay_create_time),
         success_time: Time.Format(item.success_time),
         wx_create_time: Time.Format(item.wx_create_time),
         create_by: created_by?.name || created_by?.account || null
      };
   });

   res.send(
      Result.OK({
         list,
         total: Number(total),
         pageNum: Number(laypage.pageNum),
         pageSize: Number(laypage.pageSize)
      })
   );
});

/** 查询单个用户支付信息 */
router.get("/searchOne", async (req, res) => {
   const { client_id }: PayInfo.SearchOne = req.query as any;
   if (!Check.Required([client_id])) return res.send(Result.Error("参数错误"));

   const payInfo = await prisma.pay_info.findMany({
      where: { client_id },
      select: {
         fee: true,
         actual_expenses: true,
         description: true,
         level: true,
         school: true,
         category: true,
         major: true,
         batch: true,
         school_year: true,
         status: true,
         deadline: true,
         success_time: true,
         created_time: true,
         created_by: true,
         pay_id: true,
         client_id: true,
         name: true,
         merchant: true
      }
   });

   res.send(Result.OK(payInfo));
});

/** 小程序端查询支付列表 */
router.get("/searchMp", async (req, res) => {
   // 当前 id 为小程序用户id
   const { userId } = req;

   const payInfo = await prisma.pay_info.findMany({
      where: { client_id: userId },
      select: {
         fee: true,
         description: true,
         level: true,
         school: true,
         category: true,
         major: true,
         batch: true,
         school_year: true,
         status: true,
         deadline: true,
         pay_id: true,
         client_id: true,
         merchant: true
      }
   });

   res.send(Result.OK(payInfo));
});

/** 小程序查看支付详情 */
router.get("/details", async (req, res) => {
   const { pay_id }: PayInfo.Details = req.query as any;
   if (!Check.Required([pay_id])) return res.send(Result.Error("参数错误"));

   // 查询数据库中是否存在当前订单
   const payStatus = await prisma.pay_info.findUnique({ where: { pay_id } });
   if (!payStatus) return res.send(Result.Error("当前订单不存在"));

   let payInfo: PayInfo | null;

   if (payStatus.status == "NOT_PAID") {
      payInfo = await WeChat.SearchOrderStatus({
         pay_id,
         merchant_id: payStatus.merchant
      });
   } else {
      payInfo = await prisma.pay_info.findUnique({
         where: { pay_id }
      });
   }

   if (!payInfo) return res.send(Result.Error("当前订单不存在"));

   // 查询当前 redis 中是否存在当前订单的定时器
   if (payInfo.status != "NOT_PAID") {
      let timer;
      try {
         timer = await redis.get(pay_id);
         clearTimeout(timer as string);
         redis.del(pay_id);
      } catch (error) {}
   }

   const info: Partial<PayInfo> = {
      category: payInfo.category,
      batch: payInfo.batch,
      pay_id: payInfo.pay_id,
      actual_expenses: payInfo.actual_expenses,
      success_time: Time.Format(payInfo.success_time) as null,
      deadline: payInfo.deadline,
      description: payInfo.description,
      fee: payInfo.fee,
      name: payInfo.name,
      major: payInfo.major,
      level: payInfo.level,
      merchant: payInfo.merchant,
      school: payInfo.school,
      status: payInfo.status,
      school_year: payInfo.school_year,
      wx_transaction_id: payInfo.wx_transaction_id
   };

   res.send(Result.OK(info));
});

/** 创建预支付 */
router.post("/prepayCreate", async (req, res) => {
   const { pay_id }: PayInfo.PrepayCreate = req.body;
   if (!Check.Required([pay_id])) return res.send(Result.Error("参数错误"));

   // 查询当前支付信息
   const info = await prisma.pay_info.findUnique({ where: { pay_id } });
   if (!info) return res.send(Result.Error("当前条目不存在"));

   // 查询用户信息
   const client = await prisma.client.findUnique({ where: { id: info.client_id } });
   if (!client) return res.send(Result.Error("当前客户不存在"));

   // 查询商户信息
   const merchant = await prisma.mp_merchant.findUnique({ where: { id: info.merchant } });
   if (!merchant) return res.send(Result.Error("当前条目不存在"));

   const url = "/v3/pay/transactions/jsapi";
   const timeInterval = 24 * 60 * 60 * 1000; // 时间间隔

   /** 支付备注 */
   const description = `${info.school} | ${info.category} | ${info.major}`;
   /** 支付截至时间 */
   const time_expire = Time.Format(Time.Now(), "YYYY-MM-DDTHH:mm:ssZ");

   const data = {
      appid: process.env.APPID,
      mchid: merchant.code,
      description,
      out_trade_no: info.pay_id,
      time_expire,
      notify_url: process.env.API_URL + "/api/payInfo/acceptResult",
      amount: {
         total: info.fee,
         currency: "CNY"
      },
      payer: {
         openid: client.openid
      }
   };

   // 创建微信签名
   const Authorization = WeChat.Sign({
      url,
      data,
      method: "POST",
      mchid: merchant.code,
      serialNo: merchant.serial_no,
      certKeyPath: merchant.cert_key
   });

   const prepay = await axios.post<WeChat.JSAPI>("https://api.mch.weixin.qq.com" + url, data, {
      headers: { Authorization }
   });

   const prepay_id = prepay.data.prepay_id;

   // 预支付创建成功，更新当前数据
   if (!!prepay_id) {
      await prisma.pay_info.update({
         where: { pay_id },
         data: {
            deadline: time_expire,
            description,
            prepay_id,
            status: "NOT_PAID",
            prepay_create_time: Time.Now()
         }
      });

      // 创建定时器
      // 到达截至时间时主动查询订单状态
      const timer = setTimeout(async () => {
         await WeChat.SearchOrderStatus({
            pay_id,
            merchant_id: merchant.id
         });
      }, timeInterval);

      // 将当前定时器存入redis当中
      await redis.set(pay_id, Number(timer));
   }

   const mpSign = WeChat.SignMP({
      prepay_id,
      certKeyPath: merchant.cert_key
   });

   res.send(
      Result.OK({
         prepay_id,
         ...mpSign
      })
   );
});

/** 接受微信返回信息 */
router.post("/acceptResult", async (req, res) => {
   const header = {
      wechatpayNonce: req.headers["wechatpay-nonce"],
      wechatpaySerial: req.headers["wechatpay-serial"],
      wechatpaySignature: req.headers["wechatpay-signature"],
      wechatpayTimestamp: req.headers["wechatpay-timestamp"],
      wechatpaySignatureType: req.headers["wechatpay-signature-type"]
   };

   const notification: WeChat.Notification = req.body;

   const decryptResult = WeChat.Decrypt(notification.resource);
   if (!Check.StringIsJSON(decryptResult)) return res.send({ code: "FAIL", message: "system_error" });

   const data: WeChat.ResourceDecrypt = JSON.parse(decryptResult);

   await prisma.pay_info.update({
      where: { pay_id: data.out_trade_no },
      data: {
         appid: data.appid,
         wx_create_time: new Date(notification.create_time),
         openid: data.payer?.openid,
         bank_type: data.bank_type,
         success_time: data.success_time,
         trade_type: data.trade_type,
         actual_expenses: data.amount?.payer_total,
         status: payStatusConversion[data.trade_state],
         wx_transaction_id: data.transaction_id
      }
   });

   res.send({ code: 200 });
});

/** 返回导出列 */
router.get("/exportCol", async (req, res) => {
   let list = [];

   const defaultList: string[] = [
      "phone",
      "name",
      "fee",
      "actual_expenses",
      "merchant",
      "wx_transaction_id",
      "description",
      "status",
      "deadline",
      "success_time"
   ];

   for (const key in EnumPayInfo) {
      list.push({
         code: key,
         label: EnumPayInfo[key as keyof typeof EnumPayInfo],
         sequence: defaultList.indexOf(key),
         default: defaultList.includes(key)
      });
   }

   res.send(Result.OK(list));
});

/** 列表导出 */
router.post("/export", async (req, res) => {
   const { form, laypage, title }: PayInfo.Export = req.body;

   if (!!title && title.length == 0) return res.send(Result.Error("参数错误"));

   let allSelect: string[] = [];
   for (let key in EnumPayInfo) allSelect.push(key);

   try {
      title.forEach(item => {
         if (!allSelect.includes(item)) throw new Error();
      });
   } catch (error) {
      if (error) return res.send(Result.Error("参数错误"));
   }

   res.send(Result.OK());

   const fileName = `upload/xlsx/缴费列表-${Time.Format(Time.Now(), "YYYYMMDDTHHmmss")}.xlsx`;

   const where: Prisma.pay_infoWhereInput = {
      AND: [
         !!form.name ? { phone: form.name } : {},
         !!form.level ? { level: form.level } : {},
         !!form.school ? { school: form.school } : {},
         !!form.category ? { category: form.category } : {},
         !!form.major ? { major: form.major } : {},
         !!form.status ? { status: form.status } : {},
         !!form.created_by ? { created_by: form.created_by } : {},
         !!form.deadline
            ? { AND: [{ deadline: { gte: new Date(form.deadline[0]) } }, { deadline: { lte: new Date(form.deadline[1]) } }] }
            : {},
         !!form.created_time
            ? {
                 AND: [
                    { created_time: { gte: new Date(form.created_time[0]) } },
                    { created_time: { lte: new Date(form.created_time[1]) } }
                 ]
              }
            : {},
         !!form.prepay_create_time
            ? {
                 AND: [
                    { prepay_create_time: { gte: new Date(form.prepay_create_time[0]) } },
                    { prepay_create_time: { lte: new Date(form.prepay_create_time[1]) } }
                 ]
              }
            : {}
      ]
   };

   const select: Prisma.pay_infoSelectScalar = {
      pay_id: true
   };
   const titleName: string[] = ["内部单号"];

   title.forEach(item => {
      select[item as keyof Prisma.pay_infoSelectScalar] = true;
      titleName.push(EnumPayInfo[item as keyof typeof EnumPayInfo]);
   });

   const payInfo = await prisma.pay_info.findMany({
      where,
      select: {
         pay_id: true,
         ...select
      },
      skip: (laypage.pageNum - 1) * laypage.pageSize,
      take: laypage.pageSize * 1,
      orderBy: {
         created_time: "desc"
      }
   });

   // 获取所有用户列表，并将数组转为 map
   const userList = await prisma.user.findMany();
   const userMap = new Map<string, User>();
   userList.forEach(item => userMap.set(item.id, item));

   const list = payInfo.map(item => {
      // const created_by = userMap.get(item.created_by);

      const line: any[] = [];

      Object.entries(item).forEach(([key, value]) => {
         const index = title.indexOf(key) + 1;

         line[index] = value;
         if (key == "created_by") line[index] = userMap.get(<string>value)?.name;
      });

      return [...line];
   });

   list.unshift(titleName);

   const buffer = xlsx.build([
      {
         name: "test",
         data: list,
         options: {}
      }
   ]);

   fs.writeFile(fileName, buffer, e => {
      console.log("e", e);
   });
});

export const payInfo = router;
